#include <List.h>
#include <MemoryManager.h>
#include <Interrapt.h>
#include <Proc.h>
#include <kstdio.h>

MemoryArea::MemoryArea(addr_t begin, size_t len, uint32 prot, uint32 flags, DataSource *ds){
	_begin	= begin;
	_end	= begin + len;
	_prot	= prot;
	_flags	= flags;
	_src 	= ds;
}

MemoryArea::MemoryArea(const MemoryArea &ma){
	_begin	= ma._begin;
	_end	= ma._end;
	_prot	= ma._prot;
	_flags	= ma._flags;
	_src	= ma._src;
	areas.next = NULL;
	areas.prev = NULL;
}

MemoryManager::MemoryManager(){
	_gpd = new PageTable(globalPageDir, PT_COPY_KS);
	uint32 i;
	kprintf("\n");
	for(i = 0; i < 1024; i++){
		if(_gpd->root[i] > 0){
			kprintf("*[%i]:%X,   ", i, _gpd->root[i]);
		}
	}
	kprintf("\n");
	//for(;;);
	_last_area 			= NULL;
	_search_start_addr 	= (PROC_SIZE / 3 + 0xfff) & 0xfffff000;
	kprintf("MemMan: gpd: %X, free_addr: %X\n", _gpd, _search_start_addr);
}

MemoryManager::MemoryManager(const MemoryManager &mm){
	_last_area			= NULL;
	_search_start_addr	= (PROC_SIZE / 3 + 0xfff) & (~0xfff);
	_code_start			= mm._code_start;
	_code_end			= mm._code_end;
	_data_start			= mm._data_start;
	_data_end			= mm._data_end;
	_bss_start			= mm._bss_start;
	_bss_end			= mm._bss_end;
	_stack_start		= mm._stack_start;
	
	if(mm.mas.getSize()){
		MemoryArea *nma, *oma;
		Link *lnk = mm.mas.getCHead()->next;
		while(lnk != mm.mas.getCHead()){
			oma = get_obj(lnk, MemoryArea, areas);
			nma = new MemoryArea(*oma);
			mas.insEnd(&nma->areas);
			lnk = lnk->next;
		}
	}
	
	printMap();
	_gpd = new PageTable(mm._gpd, PT_COPY_AS | PT_MARK_RO);
	
	
}

MemoryArea * MemoryManager::findArea(addr_t addr){
	if(_last_area && _last_area->getEnd() > addr && _last_area->getBegin() <= addr)
		return _last_area;
	MemoryArea *ma;
	Link *lnk = mas.getHead()->next;
	while(lnk != mas.getHead()){
		ma = get_obj(lnk, MemoryArea, areas);
		if(ma && ma->getEnd() > addr){
			_last_area = ma;
			return ma;
		}else{
			lnk = lnk->next;
		}
	}
	return NULL;
}

addr_t MemoryManager::findFreeBlock(addr_t start, size_t sz){
	if(sz > PROC_SIZE)
		return NULL;
	MemoryArea *ma;
	addr_t addr = start & 0xfffff000;
	if(addr && addr + sz <= PROC_SIZE){
		
		ma = findArea(addr);
		//kprintf("First: %X, %X\n", ma, ma->getBegin());
		if(!ma || addr + sz <= ma->getBegin()){
			//kprintf("Find free area first\n");
			return addr;
		}
	}
	addr_t start_addr = addr = _search_start_addr;
	for(ma = findArea(addr);;){
		if(addr + sz > PROC_SIZE){
			if(start_addr == (PROC_SIZE / 3 + 0xfff) & 0xfffff000)
				return NULL;
			start_addr = addr = (PROC_SIZE / 3 + 0xfff) & 0xfffff000;
			ma = findArea(addr);
		}
		if(!ma || addr + sz <= ma->getBegin()){
			_search_start_addr = addr + sz;
			return addr;
		}
		addr = ma->getEnd();
	}
}

#define	MAALLOC	
MemoryArea * MemoryManager::createArea(addr_t begin, uint32 size, uint32 prot, uint32 flags, DataSource *ds, MemoryArea *after){
	MemoryArea *ma = findArea(begin), *cur_ma;
	//kprintf("2: MA=%X\n", ma);
	cur_ma = new(MAALLOC) MemoryArea(begin, size, prot, flags, ds);
	Link *nextMA;
	if(ma){
		nextMA = &ma->areas;
	}else{
		nextMA = mas.getHead();
	}
	mas.insBefor(&cur_ma->areas, nextMA);
	//Подготовка таблиц страниц для области
	return cur_ma;
}

MemoryArea * MemoryManager::memoryMap(void *addr, size_t len, int prot, int flags, DataSource *ds, int32 offset){
	MemoryArea *ma;
	//kprintf("IN ADDR: %X\n", addr);
	//Размер отображаемой области округляем до размера страницы в большую сторону
	addr_t start_addr = (addr_t) addr & 0xfffff000;
	len = len + ((addr_t) addr - start_addr);
	size_t map_len = (len + 0xfff) & 0xfffff000;
	kprintf("\nLen: %X, MapLen: %X\n", len, map_len);
	addr_t freeBlock = findFreeBlock((addr_t)addr, map_len);
	//kprintf("MM(findFreeBlock):addr=%X, freeBlock=%X\n",addr,freeBlock);
	if(freeBlock){
			ma = createArea(freeBlock, map_len, prot, flags, ds);
			return ma;
	}else{
		kprintf("No mem!\n");
	}
	return NULL;
}

uint32 MemoryManager::memoryUnMap(void *addr, size_t len){
	addr_t start = (addr_t)addr & (~0xfff);
	MemoryArea *old_ma = findArea(start);
	addr_t end = start + ((len + 0xfff) & (~0xfff));
	if(old_ma && old_ma->getBegin() <= start){
		//kprintf("block find\n");
		bool sflag = (start == old_ma->getBegin());
		bool eflag = (end == old_ma->getEnd());
		mas.remove(&old_ma->areas);
		if(getInUse() == old_ma) setInUse(NULL);

		if(sflag && eflag){
			//kprintf("remove only\n");
			delete old_ma;
			return 0;
		}else if(sflag){
			//kprintf("remove begin: start: %X, size: %X\n", end, old_ma->getEnd() - end);
			memoryMap((void *)end, old_ma->getEnd() - end, old_ma->getProt(), 2, old_ma->getDataSource(), 0);
			delete old_ma;
			return 1;
		}else if(eflag){
			//kprintf("remove end\n");
			memoryMap((void *)old_ma->getBegin(), start - old_ma->getBegin(), old_ma->getProt(), 2, old_ma->getDataSource(), 0);
			delete old_ma;
			return 2;
		}
		//kprintf("remove middle\n");
		memoryMap((void *)old_ma->getBegin(), start - old_ma->getBegin(), old_ma->getProt(), 2, old_ma->getDataSource(), 0);
		memoryMap((void *)end, old_ma->getEnd() - end, old_ma->getProt(), 2, old_ma->getDataSource(), 0);
		delete old_ma;
		return 3;
	}
	return 4;
}

void MemoryManager::printMap(){
	MemoryArea *cur_ma;
	Link *lnk = mas.getHead()->next;
	uint32 i = 0;
	while(lnk != mas.getHead()){
		cur_ma = get_obj(lnk, MemoryArea, areas);
		kprintf("MA[%i]: begin: %X, end %X\n", i, cur_ma->getBegin(), cur_ma->getEnd());
		lnk = lnk->next;
		i++;
	}
}


uint32 MemoryManager::pageFoult(addr_t addr, uint32 error){
	MemoryArea *ma = findArea(addr);
	if(ma && ma->getBegin() <= addr){
		uint32 pgclone = testbit(error, 1), flags = 0;
		kprintf("P: %i, RW: %i\n", testbit(error, 0), testbit(error, 1));

		//kprintf("ds:%X\n", ma->getDataSource());
		Frame *frm, *old_frm;
		if(!ma->getDataSource()){
			//Если для области не определен источник данных, т.е. область анонимная,
			//выделяем пустой фрейм.
			frm = zone_normal->_allocFrame(0);
		}else{
			//Область принадлежит отображению в файл
			old_frm = ma->getDataSource()->getFrame();
			if(0){
				frm = zone_normal->_allocFrame(0);
				memcpy(_va(_frm_to_addr(old_frm)), _va(_frm_to_addr(frm)), 4096);
				memoryUnMap((void *)addr, 4096);
				ma = memoryMap((void *)addr, 4096, MA_WRITE | MA_PRIVATE, 2, NULL, 0);
			}else{
				frm = old_frm;
			}
		}
		flags = (testbit(ma->getProt(), 0) << 1) | 1 | 4; 
		//kprintf("Good page!:%X\n", frm);
		_gpd->mapPage(addr, frm, flags);
		//kprintf("page mapped!\n");
		_gpd->flushTLB();
		//for(;;);
	}else{
		kprintf("Bad page!\n");
		printMap();
		
		for(;;);
	}
	//kprintf("MM: page foult out\n");
}


void pageFoultInterrapt(){
	uint32	error;
	reg_t	*reg;
	int_err_in(error, reg);
	addr_t address = 0;
	asm("movl %%cr2, %0":"=r" (address));
	kprintf("FOULT_ADDRESS: %X, ProcAddr: %X, PID: %i, ThreadAddr: %X, TID:%i, Error_code: %X\n", 
		address, 
		current->getOwner(), 
		current->getOwner()->getPid(), 
		current, 
		current->getTid(), 
		error);
	Proc *prc = current->getOwner();
	prc->mm->pageFoult(address, error);
	uint32 i;
	for(i = 0; i < 1024; i++){
		if(prc->mm->getGPD()->root[i] > 0){
			kprintf("[%i] %X    ", i, prc->mm->getGPD()->root[i]);
		}
	}
	int_err_out();
}



	
